package de.preamp.clients.java.player;

import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import de.preamp.clients.java.data.Track;
import de.preamp.clients.java.net.LastFmScrobbler;
import de.preamp.clients.java.net.LastFmScrobbler.ScrobblingException;
import de.preamp.clients.java.playlist.Playlist;
import de.preamp.clients.java.playlist.PlaylistInMemoryImpl;
import de.preamp.clients.java.playlist.PlaylistManager;

/**
 * This is the actual player component used to play the music files. It is a
 * kind of proxy class for {@link PreampPlayer}.
 * 
 * @author André Stannek
 * @version 2011-06-11
 */
@Component("playbackManager")
public class PlaybackManager implements PlayerControls, PreampPlayerListener // NOPMD
{ // NOPMD
    /**
     * Actual player.
     */
    private final transient PreampPlayer player;

    /**
     * scrobbler.
     */
    @Autowired(required = true)
    private transient LastFmScrobbler scrobbler;

    /**
     * The time a track was last scrobbled to "nowPlaying".
     */
    private transient long lastScrobbled;

    /**
     * The time the currently played track was started.
     */
    private transient long startTime;

    /**
     * true if the currently played track was already scrobbled.
     */
    private transient boolean scrobbled;

    /**
     * 
     */
    private transient PlaylistCacher playlistCache;

    /**
     * 
     */
    private transient Playlist playlist;

    /**
     * listeners to notify.
     */
    private final transient Set<PlaybackListener> listeners;

    /**
     * 
     */
    @Autowired(required = true)
    private PlaylistManager playlistManager;

    /**
     * 
     */
    private final Playlist currentlyPlaying;

    /**
     * 
     */
    public PlaybackManager()
    {
	super();
	player = new PreampPlayer();
	player.addBasicPlayerListener(this);
	// TODO replace dummy playlist
	currentlyPlaying = new PlaylistInMemoryImpl("dummy");
	// player.setLineBufferSize(PreferencesInFile.instance()
	// .getCacheSize() * 1000);
	player.setLineBufferSize(10000);
	lastScrobbled = System.currentTimeMillis();
	listeners = new HashSet<PlaybackListener>();
    }

    @Override
    public final void playPauseToggle() throws UnsupportedAudioFileException,
	    IOException, LineUnavailableException, PreampPlayerException
    {
	final PlayerState status = player.getStatus();
	if (status.equals(PlayerState.PLAYING))
	{
	    player.pause();
	}
	else if (status.equals(PlayerState.PAUSED))
	{
	    player.resume();

	}
	else
	{
	    playlist = playlistManager.getSelectedPlaylist();
	    playlistCache = new PlaylistCacher(playlist);
	    player.open(playlistCache.getStream(playlist.getCurrentTrack()));
	    player.play();
	    onTrackChange();
	}
    }

    @Override
    public final void previous() throws UnsupportedAudioFileException,
	    IOException, LineUnavailableException, PreampPlayerException
    {
	player.stop();
	player.open(playlistCache.getStream(playlist.getPreviousTrack()));
	player.play();
	onTrackChange();
    }

    @Override
    public final void next() throws UnsupportedAudioFileException, IOException,
	    LineUnavailableException, PreampPlayerException
    {
	player.stop();
	player.open(playlistCache.getStream(playlist.getNextTrack()));
	player.play();
	onTrackChange();
    }

    @Override
    public final void stop()
    {
	player.stop();
    }

    @Override
    public final void seek(final Integer length)
	    throws LineUnavailableException
    {
	try
	{
	    player.seek(length.longValue());
	}
	catch (final PreampPlayerException e)
	{
	    stop();
	}
    }

    /**
     * This method is called when a track changes.
     */
    private void onTrackChange()
    {
	startTime = System.currentTimeMillis() / 1000L;
	scrobbled = false;
    }

    @Override
    public final void addPlaybackListener(final PlaybackListener listener)
    {
	listeners.add(listener);
    }

    @Override
    public final void removePlaybackListener(final PlaybackListener listener)
    {
	listeners.remove(listener);
    }

    @Override
    public void opened(final Object stream, final Map<String, Object> properties)
    {
	// not needed
    }

    @Override
    public final void progress(final int bytesread, final long microseconds,
	    final byte[] pcmdata, final Map<String, Object> properties)
    {
	final Track track = currentlyPlaying.getCurrentTrack();
	try
	{
	    if (System.currentTimeMillis() - lastScrobbled > 9500)
	    {
		scrobbler.nowPlaying(track);
		lastScrobbled = System.currentTimeMillis();
	    }
	    // optimized microseconds > (track.getLength() * 1000L) / 2L
	    if (!scrobbled && microseconds > track.getLength() * 500L)
	    {
		scrobbler.submit(track, startTime);
		scrobbled = true;
	    }
	}
	catch (final ScrobblingException e)
	{
	    for (final PlaybackListener listener : listeners)
	    {
		listener.exceptionOccured(e);
	    }
	}
	for (final PlaybackListener listener : listeners)
	{
	    listener.progress(bytesread, microseconds);
	}

    }

    @Override
    public void setController(final PreampPlayerOperations arg0)
    {
	// not needed
    }

    @Override
    public final void stateUpdated(final PreampPlayerEvent event) // NOPMD
    {
	final Track track = currentlyPlaying.getCurrentTrack();
	switch (event.getType())
	{
	case STOPPED:
	    break;
	case EOM:
	    if (!scrobbled && event.getPosition() > track.getLength() * 500L)
	    {
		try
		{
		    scrobbler.submit(track, startTime);
		    scrobbled = true;
		}
		catch (final ScrobblingException e)
		{
		    for (final PlaybackListener listener : listeners)
		    {
			listener.exceptionOccured(e);
		    }
		}
	    }
	    try
	    {
		next();
	    }
	    catch (final UnsupportedAudioFileException e)
	    {
		for (final PlaybackListener listener : listeners)
		{
		    listener.exceptionOccured(e);
		}
	    }
	    catch (final IOException e)
	    {
		for (final PlaybackListener listener : listeners)
		{
		    listener.exceptionOccured(e);
		}
	    }
	    catch (final LineUnavailableException e)
	    {
		for (final PlaybackListener listener : listeners)
		{
		    listener.exceptionOccured(e);
		}
	    }
	    catch (final PreampPlayerException e)
	    {
		for (final PlaybackListener listener : listeners)
		{
		    listener.exceptionOccured(e);
		}
	    }
	    break;
	default:
	    break;
	}
	for (final PlaybackListener listener : listeners)
	{
	    listener.stateChanged(track, event);
	}
    }
}
